package com.itcoon.cloud.framework.mybatis.interseptor;

import com.itcoon.cloud.framework.utils.TemporalConvertUtils;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.temporal.Temporal;
import java.util.List;
import java.util.regex.Matcher;


@Intercepts({
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class SqlLogInterceptor implements Interceptor {

    public static final Logger LOG = LoggerFactory.getLogger(SqlLogInterceptor.class);

    private static String getParameterValue(Object object) {
        String value = "";
        if(object != null){
            if (object instanceof String){
                value = "'" + object + "'";
            }else if (object instanceof Temporal) {
                Object s = "";
                if (object instanceof LocalDate) {
                    s = TemporalConvertUtils.format((LocalDate) object);
                } else if (object instanceof LocalDateTime) {
                    s = TemporalConvertUtils.format((LocalDateTime) object);
                } else if (object instanceof ZonedDateTime) {
                    s = TemporalConvertUtils.format((ZonedDateTime) object);
                }
                if(s instanceof String){
                    value = "'" + s + "'";
                }else{
                    value = String.valueOf(s);
                }
            }else{
                value = String.valueOf(object);
            }
        }
        return value;
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StopWatch watch = new StopWatch("执行sql耗时");
        watch.start();
        Object proceed = invocation.proceed();
        watch.stop();
        String printSql = "";
        try {
            printSql = generateSql(invocation);
        }finally {
            LOG.info("执行Sql: {}", printSql);
        }
        return proceed;
    }

    private String generateSql(Invocation invocation) {
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = null;
        if(invocation.getArgs().length > 1){
            parameter = invocation.getArgs()[1];
        }
        Configuration configuration = statement.getConfiguration();
        BoundSql boundSql = statement.getBoundSql(parameter);

        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> params = boundSql.getParameterMappings();
        String sql = boundSql.getSql();
        sql = sql.replaceAll("\\s+", " ");
        if(!ObjectUtils.isEmpty(parameter) && !ObjectUtils.isEmpty(parameterObject)){
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())){
                sql = sql.replaceFirst("\\?", parameterObject.toString());
            }else{
                for (ParameterMapping param : params) {
                    String paramName = param.getProperty();
                    MetaObject metaObject = configuration.newMetaObject(parameterObject);
                    if(metaObject.hasGetter(paramName)){
                        Object obj = metaObject.getValue(paramName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    }else if(boundSql.hasAdditionalParameter(paramName)){
                        Object obj = boundSql.getAdditionalParameter(paramName);
                        sql = sql.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
                    }else{
                        sql = sql.replaceFirst("\\?", "Missing");
                    }
                }
            }
        }
        return sql;
    }


}
